package pkg.andru.concurrent;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import pkg.andru.util.Log;

/**
 * a simple non-blocking thread pool
 * 
 * @author idiottiger
 * 
 */
public class NonBlockingThreadPool {

    static final String LOG_TAG = "NonBlockingThreadPool";

    static final int DEFAULT_POOL_SIZE = 3;

    AtomicInteger mThreadCounter = new AtomicInteger(0);

    ArrayList<InnerThread> mThreadsList;

    int mThreadSize;

    /**
     * use default thread size to init
     */
    public NonBlockingThreadPool() {
        this(DEFAULT_POOL_SIZE);
    }

    /**
     * use your defined thread size to init
     * 
     * @param threadSize
     * @throws IllegalArgumentException
     *             if the thread size <=0 will throw the exception
     */
    public NonBlockingThreadPool(int threadSize) {
        if (threadSize <= 0) {
            throw new IllegalArgumentException("thread size need bigger than zero");
        }
        mThreadSize = threadSize;
        mThreadsList = new ArrayList<InnerThread>();
    }

    /**
     * execute the runnable in thread, if the thread pool is full, will ingore
     * this quest
     * 
     * @param runnable
     */
    public void execute(Runnable runnable) {
        allocateOrCreateThreadToWork(runnable);
    }

    /**
     * shut down the running thread and release the memory
     */
    public void shutDown() {
        synchronized (mThreadsList) {
            for (InnerThread ithread : mThreadsList) {
                ithread.shutDown();
            }
            mThreadsList.clear();
        }
    }

    /**
     * check the pool is full or not
     * 
     * @return true is full, otherwise is not full
     */
    public boolean isPoolFull() {
        boolean result = true;
        synchronized (mThreadsList) {
            final int size = mThreadsList.size();
            if (size >= mThreadSize) {
                for (InnerThread ithread : mThreadsList) {
                    if (ithread.isFree()) {
                        result = false;
                        break;
                    }
                }
            } else {
                result = false;
            }
        }
        return result;
    }

    private void allocateOrCreateThreadToWork(Runnable runnable) {
        synchronized (mThreadsList) {
            final int size = mThreadsList.size();
            if (size < mThreadSize) {
                final InnerThread thread = new InnerThread();
                mThreadsList.add(thread);
                thread.workRunning(runnable);
            } else {
                final InnerThread freeThread = getFreeInnerThread();
                if (freeThread == null) {
                    Log.w(LOG_TAG, "DON'T have free thread, so ignore the run request...");
                } else {
                    freeThread.workRunning(runnable);
                }
            }
        }
    }

    InnerThread getFreeInnerThread() {
        for (InnerThread ithread : mThreadsList) {
            if (ithread.isFree()) {
                return ithread;
            }
        }
        return null;
    }

    /**
     * @author idiottiger
     */
    class InnerThread extends Thread {

        volatile boolean isShutDown = false;

        volatile boolean isFree = true;

        AtomicReference<Runnable> runner = new AtomicReference<Runnable>();

        private Lock lock = new ReentrantLock();

        private Condition condition = lock.newCondition();

        public InnerThread() {
            start();
        }

        @Override
        public void run() {
            final int threadId = mThreadCounter.incrementAndGet();
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
            setName("bpool " + threadId);
            while (!isShutDown) {
                if (isFree && !isShutDown) {
                    try {
                        sleepDown();
                    } catch (InterruptedException e) {
                    }
                }
                if (!isFree && !isShutDown) {
                    final Runnable runnable = runner.get();
                    if (runnable != null) {
                        runnable.run();
                    }
                    runner.set(null);
                    isFree = true;
                }
            }
        }

        public void workRunning(Runnable runnable) {
            runner.set(runnable);
            wakeup();
        }

        public boolean isFree() {
            return isFree;
        }

        public void shutDown() {
            isShutDown = true;
            wakeup();
        }

        private void wakeup() {
            isFree = false;
            lock.lock();
            try {
                condition.signalAll();
            } finally {
                lock.unlock();
            }
        }

        private void sleepDown() throws InterruptedException {
            lock.lock();
            try {
                condition.await();
            } finally {
                lock.unlock();
            }
        }

    }

}